﻿using System;
using ActivateYourGlutes.Model.Providers;
using NUnit.Framework;
using Rhino.Mocks;

namespace ActivateYourGlutes.Model.Tests.Unit
{
    [TestFixture]
    public class Test_ProviderBase
    {
        /// <summary>
        /// ProviderBase is the subject of our tests but its abstract so can't be instantiated itself.
        /// We derive a basic class from it so that we can test it.
        /// </summary>
        private class ProviderBaseInheritedForTest : ProviderBase
        {
            public ProviderBaseInheritedForTest(IEntityModelFactory factory)
                : base(factory)
            {
            }

            /// <summary>
            /// Since we need to override from ProviderBase to instantiate it we can make the protected
            /// member available for testing by adding a public wrapper.
            /// </summary>
            public void PublicTryUpdateModel(object destination, object source, string[] properties)
            {
                TryUpdateModel(destination, source, properties);
            }
        }

        private ProviderBaseInheritedForTest _provider;

        [SetUp]
        public void Init()
        {
            var stubModelFactory = MockRepository.GenerateStub<IEntityModelFactory>();
            _provider = new ProviderBaseInheritedForTest(stubModelFactory);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Test_TryUpdateModel_NullDestinationException()
        {
            _provider.PublicTryUpdateModel(null, new object(), new[] {"SomeProperty" } );
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Test_TryUpdateModel_NullSourceException()
        {
            _provider.PublicTryUpdateModel(new object(), null, new[] { "SomeProperty" });
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Test_TryUpdateModel_NullPropertiesException()
        {
            _provider.PublicTryUpdateModel(new object(), new object(), null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Test_TryUpdateModel_EmptyPropertiesException()
        {
            _provider.PublicTryUpdateModel(new object(), new object(), new string[] { });
        }

        [Test]
        [ExpectedException(typeof(ModelException))]
        public void Test_TryUpdateModel_CannotFindSourcePropertyException()
        {
            _provider.PublicTryUpdateModel(new Source(), new Destination(), new[] { "OnlyOnDestination" });
        }

        [Test]
        [ExpectedException(typeof(ModelException))]
        public void Test_TryUpdateModel_CannotFindDestinationPropertyException()
        {
            _provider.PublicTryUpdateModel(new Source(), new Destination(), new[] { "OnlyOnSource" });
        }

        [Test]
        [ExpectedException(typeof(ModelException))]
        public void Test_TryUpdateModel_MismatchedPropertyTypeException()
        {
            _provider.PublicTryUpdateModel(new Source(), new Destination(), new[] { "DifferingType" });
        }

        [Test]
        public void Test_TryUpdateModel_SuccessfulUpdate()
        {
            var source = new Source() {PropertyOne = "Hello World", PropertyTwo = 42};
            var destination = new Destination() { PropertyOne = "Should be overwritten", PropertyTwo = 1984 };

            _provider.PublicTryUpdateModel(source, destination, new[] { "PropertyOne", "PropertyTwo" });

            Assert.AreEqual(source.PropertyOne, destination.PropertyOne);
            Assert.AreEqual(source.PropertyTwo, destination.PropertyTwo);
        }

        [Test]
        public void Test_TryUpdateModel_SuccessfulPartialUpdate()
        {
            var source = new Source() { PropertyOne = "Hello World", PropertyTwo = 42 };
            var destination = new Destination() { PropertyOne="Should be overwritten", PropertyTwo = 1984 };

            _provider.PublicTryUpdateModel(source, destination, new[] { "PropertyOne" });

            Assert.AreEqual(source.PropertyOne, destination.PropertyOne);
            Assert.AreNotEqual(source.PropertyTwo, destination.PropertyTwo);
        }
    }
}
